package antidestiny.java2dex.ui.activity;

import android.content.Intent;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import antidestiny.java2dex.Jing;
import antidestiny.java2dex.R;
import antidestiny.utilib.Aide;
import antidestiny.utilib.io.FileUtils;
import antidestiny.utilib.ui.fragment.explorer.ExplorerFileFilter;
import antidestiny.utilib.ui.fragment.explorer.ExplorerFragment;
import antidestiny.utilib.ui.fragment.explorer.ExplorerManagerI;
import antidestiny.utilib.ui.fragment.explorer.FileItem;
import dalvik.system.DexFile;

public class TerminalActivity extends AppCompatActivity implements View.OnKeyListener, ExplorerManagerI {
    String dexFilePath;
    Class<?> mainClass;
    final int CHOOSE_DEX_REQUEST = 1;
    String[] args;
    ExplorerFragment explorerFragment;
    PrintStream printStream, oldStream = System.out;
    TerminalInputStream terminalInputStream = new TerminalInputStream();
    EditText editArgs, editCommand;
    TextView textOutput;

    MyLoader myLoader;

    protected class MyLoader extends ClassLoader {
        public MyLoader(ClassLoader parent) {
            super(parent);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        myLoader = new MyLoader(getClassLoader());
        setContentView(R.layout.activity_terminal);
        editArgs = ((EditText) findViewById(R.id.editText_args));
        editArgs.addTextChangedListener(onArgsTextChangeListener);
        editCommand = ((EditText) findViewById(R.id.edit_command));
        editCommand.setOnKeyListener(this);
        textOutput = ((TextView) findViewById(R.id.text_output));
        textOutput.setTypeface(Typeface.createFromAsset(getAssets(), "fonts/Consolas.ttf"));

        final TextPrintStream textPrintStream;
        textPrintStream = new TextPrintStream(textOutput);
        printStream = new PrintStream(textPrintStream);
        System.setOut(printStream);
        System.setErr(printStream);
        System.setIn(terminalInputStream);

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        this.explorerFragment = ExplorerFragment.newInstance(
                Environment.getExternalStorageDirectory()
                , (new File(Environment.getExternalStorageDirectory().getAbsolutePath())).getParentFile()
                , this);
        explorerFragment.setTitle("请选择jar文件或者dex文件，其中jar文件必须已经dex化。选择的文件需要有主函数入口。");
        transaction.replace(R.id.container, explorerFragment).hide(explorerFragment)
                .commit();
        Intent intent = getIntent();
        dexFilePath = intent.getStringExtra(Jing.DEX_FILE);
        File dexFile = dexFilePath == null ? null : new File(dexFilePath);
        if (dexFilePath == null || dexFilePath.length() == 0
                || !dexFile.isFile()) {
            chooseFile();
        } else {
            setTitleText();
            chooseMain();
        }
    }

    void setTitleText() {
        ((TextView) findViewById(R.id.textView_title)).setText(dexFilePath);
    }

    void run() throws Throwable {
        run(mainClass);
    }

    void run(final Class<?> main) throws Throwable {
        if (mainClass == null) {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle(R.string.error)
                    .setMessage("main函数没有设置，无法运行").show();
        }


        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                View button = findViewById(R.id.button_run);
                try {
                    System.setOut(printStream);
                    button.setClickable(false);
                    System.out.println("调用主函数");
                    Aide.t("invoke");
                    main.getMethod("main", String[].class).invoke(null, (Object) (args));
                    System.out.println("主函数运行结束");
                    Aide.t("finish" + textOutput.getText());
                } catch (InvocationTargetException e) {
                    e.getTargetException().printStackTrace();
                    Aide.t("外部目标程序执行错误：" + e);
                } catch (Throwable t) {
                    t.printStackTrace();
                    Aide.t("发生未预料到的错误：" + t);
                } finally {
                    button.setClickable(true);
                }
            }
        });
        thread.start();
        //printStream.close();


    }

    public void chooseFile() {
        Aide.t("选择运行文件");
        findViewById(R.id.container).setVisibility(View.VISIBLE);
        getSupportFragmentManager().beginTransaction().show(explorerFragment).commit();
        findViewById(R.id.parent).setVisibility(View.GONE);
        explorerFragment.chooseRequest(CHOOSE_DEX_REQUEST, ExplorerFileFilter.TYPE_FILE_FILTER
                , null, new FileFilter() {
                    @Override
                    public boolean accept(File pathname) {
                        return FileUtils.suffixCompare(pathname, ".jar") ||
                                FileUtils.suffixCompare(pathname, "dex") ||
                                FileUtils.suffixCompare(pathname, ".apk") ||
                                pathname.isDirectory();
                    }
                });

    }

    void chooseMain() {
        Aide.t("选择main文件");
        if (dexFilePath == null) {
            Aide.e("选择时没有找到dexFilePath。");
            return;
        }
        try {
            final DexFile dexFile = new DexFile(dexFilePath);
            Enumeration<String> entries = dexFile.entries();
            ArrayList<Class<?>> classes = new ArrayList<>();
            final ArrayList<Class<?>> mainClasses = new ArrayList<>();
            while (entries.hasMoreElements()) {
                classes.add(dexFile.loadClass(entries.nextElement(), myLoader));
            }
            for (Class<?> clz : classes) {
                Method[] methods = clz.getMethods();
                for (Method method : methods) {
                    if (method.getName().equals("main"))
                        mainClasses.add(clz);
                }
            }
            if (mainClasses.size() == 0) {
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setTitle("错误！")
                        .setMessage("没有找到main函数")
                        .setPositiveButton(R.string.ok, null).show();
                return;
            } else if (mainClasses.size() == 1) {
                Aide.t("只有一个可能的入口：" + mainClasses.get(0));
                mainClass = mainClasses.get(0);
                ((TextView) findViewById(R.id.textView_title)).setText(mainClass.getName());
                return;
            }
            final AlertDialog alertDialog;
            ListView listView = new ListView(this);
            final ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mainClasses);
            listView.setAdapter(adapter);
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            alertDialog = builder.setTitle("选择入口")
                    .setView(listView).create();
            listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    alertDialog.dismiss();
                    mainClass = (Class<?>) adapter.getItem(position);
                    Aide.t("入口已选择：" + mainClass);
                    ((TextView) findViewById(R.id.textView_title)).setText(mainClass.getName());
                }
            });
            alertDialog.show();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onChooseResult(int chooseRequest, @Nullable List<FileItem> selectedFiles) {
        getSupportFragmentManager().beginTransaction().hide(explorerFragment).commit();
        findViewById(R.id.container).setVisibility(View.GONE);
        findViewById(R.id.parent).setVisibility(View.VISIBLE);
        if (selectedFiles == null) return;
        switch (chooseRequest) {
            case CHOOSE_DEX_REQUEST:
                dexFilePath = selectedFiles.get(0).path;
                setTitleText();
                if (mainClass == null)
                    chooseMain();
                break;

        }
    }

    @Override
    public void onPasteBoardChange(@Nullable File file) {

    }

    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button_run:
                try {
                    run();
                } catch (Throwable e) {
                    e.printStackTrace();
                }
                break;
            case R.id.text_output:
                editCommand.performClick();
                break;
        }
    }

    final TextWatcher onArgsTextChangeListener = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {

        }

        @Override
        public void afterTextChanged(Editable s) {
            TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
            splitter.setString(s.toString());
            Iterator<String> strings = splitter.iterator();
            ArrayList<String> args = new ArrayList<>();
            while (strings.hasNext())
                args.add(strings.next());
            TerminalActivity.this.args = new String[args.size()];
            for (int i = 0; i < args.size(); i++) {
                TerminalActivity.this.args[i] = args.get(i);
            }
        }
    };

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_BACK:
                if (!explorerFragment.isHidden()) {
                    explorerFragment.back();
                    return true;
                }
                break;
        }
        return super.onKeyUp(keyCode, event);
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        System.setOut(oldStream);
        System.setErr(oldStream);
        printStream.close();
    }

    //view上的按键信息
    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        switch (v.getId()) {
            case R.id.edit_command:
                if (keyCode == KeyEvent.KEYCODE_ENTER) {
                    String command = editCommand.getText().toString();
                    terminalInputStream.putToRead(command);
                    editCommand.setText("");
                    return true;
                }
                break;

        }
        return false;
    }

    class TextPrintStream extends ByteArrayOutputStream {
        TextView textView;

        public TextPrintStream(TextView textView) {
            this.textView = textView;
        }

        public void freshTextView() {
            final String text = new String(this.toByteArray());
            Aide.t("postFresh");
            textView.post(new Runnable() {
                @Override
                public void run() {
                    textView.setText(text);
                    Aide.t("setText");
                }
            });
        }

        @Override
        public synchronized void write(byte[] buffer, int offset, int len) {
            super.write(buffer, offset, len);
            freshTextView();
        }

        @Override
        public void write(@NonNull byte[] buffer) throws IOException {
            super.write(buffer);
            freshTextView();
        }

        @Override
        public synchronized void write(int oneByte) {
            super.write(oneByte);
            freshTextView();
        }
    }

    class TerminalInputStream extends InputStream {
        final Queue<Integer> tempRead = new LinkedList<>();
        CharBuffer charBuffer = CharBuffer.allocate(1);
        ByteBuffer byteBuffer = ByteBuffer.allocate(5);
        CharsetEncoder charsetEncoder = Charset.defaultCharset().newEncoder();

        public void putToRead(String str) {
            synchronized (tempRead) {
                for (int i = 0; i < str.length(); i++) {
                    charBuffer.rewind();
                    charBuffer.append(str.charAt(i));
                    charBuffer.rewind();
                    byteBuffer.rewind();
                    charsetEncoder.encode(charBuffer, byteBuffer, false);
                    int pos = byteBuffer.position();
                    byteBuffer.rewind();
                    for (int j = 0; j < pos; j++)
                        tempRead.add((int) byteBuffer.get());
                }
                tempRead.notifyAll();
            }
        }

        boolean close = false;

        @Override
        public int read() {
            if (close) return -1;
            synchronized (tempRead) {
                try {
                    if (tempRead.size() == 0) tempRead.wait();
                } catch (InterruptedException e) {
                }
            }
            return (Integer) tempRead.poll();
        }

        @Override
        public int read(byte[] b, int off, int len) {
            if (close) return -1;
            synchronized (tempRead) {
                try {
                    if (tempRead.size() == 0) tempRead.wait();
                } catch (InterruptedException e) {
                }
            }
            int i = 0;
            while (!tempRead.isEmpty() && i < len) {
                b[off + i] = (byte) ((Integer) tempRead.poll()).intValue();
                i++;
            }
            return i;
        }

        @Override
        public void close() {
            close = true;
        }

    }

}
